﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using System.Windows.Forms;
using Logging;
using EntityModel;
using ApplicationConfiguration;

namespace Server.TcpIp
{
    public class TcpServerModel : StatusProvider
    {
        private static readonly SimpleLogger logger = SimpleLogger.getLogger(typeof(TcpServerModel));
        private static MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();

        public class TcpClientStatusProvider : StatusProvider
        {
            private DateTime startTime;
            private DateTime lastAccessTime;

            public TcpClientStatusProvider(TcpClient client)
            {
                startTime = DateTime.Now;
                lastAccessTime = startTime;
            }

            public DateTime LastAccessTime
            {
                get
                {
                    return lastAccessTime;
                }
                set
                {
                    lastAccessTime = value;
                }
            }
            
            public DateTime getStartTime()
            {
                return startTime;
            }

            public DateTime getLastAccessTime()
            {
                return lastAccessTime;
            }

            public ICollection<StatusProvider> getChildren()
            {
                return new List<StatusProvider>();
            }
        }

        #region Constants
        /// <summary>
        /// The maximum size of an message
        /// </summary>
        private const int MAX_MESSAGE_SIZE = 8192;
        /// <summary>
        /// An id used for testing. This should be removed later.
        /// </summary>
        private const string TEST_ID = "50";
        #endregion

        #region Members
        private TeleasistentaEntities entities;
        /// <summary>
        /// Used for detecting new clients trying to logion
        /// </summary>
        private TcpListener _listener;
        /// <summary>
        /// The port used for connecting to the server
        /// </summary>
        private int _port;
        /// <summary>
        /// The thread that is used to listen to incoming TCP requests
        /// </summary>
        private Thread _listenerThread;
        /// <summary>
        /// Used for starting/stoping the TCP/IP server
        /// </summary>
        private volatile bool _isOn;

        private DateTime startTime;

        private DateTime lastAccessTime;

        private List<StatusProvider> children;
        #endregion

        #region Events
        public event MsgReceivedEventHandler MsgReceived;
        #endregion

        #region Virtual methods
        protected virtual void OnMsgReceived(MsgReceivedEventArgs e)
        {
            if (MsgReceived != null)
            {
                MsgReceived(this, e);
            }
        }
        #endregion

        #region Constructors
        public TcpServerModel(int port)
        {
            entities = new TeleasistentaEntities(AppConfiguration.getInstance().getProperty("Database", "connectionString"));
            children = new List<StatusProvider>();
            logger.info("Sensor processing endpoint port: " + port);
            _port = port;
            logger.info("Creating TCP listener for sensor processing endpoint...");
            _listener = new TcpListener(IPAddress.Any, _port);
            _isOn = false;
            _listenerThread = new Thread(new ThreadStart(ListenForClients));
            _listenerThread.IsBackground = false;
            logger.info("Sensor processing endpoint listener succesfully created");
        }
        #endregion

        #region Private Methods
        private void ListenForClients()
        {
            _listener.Start();
            try
            {
                while (_isOn)
                {
                    //blocks until a client has connected to the server
                    TcpClient client = _listener.AcceptTcpClient();
                    //create a thread to handle communication with connected client
                    logger.info("Sensor module connected. Identifying...");
                    lastAccessTime = DateTime.Now;
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
                    clientThread.IsBackground = true;
                    clientThread.Start(client);
                }
            }
            catch
            {
                Thread.CurrentThread.Abort();
            }
        }

        private void HandleClient(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            TcpClientStatusProvider tcpClientStatusProvider = new TcpClientStatusProvider(tcpClient);
            NetworkStream clientStream = tcpClient.GetStream();

            byte[] message = new byte[MAX_MESSAGE_SIZE];
            int bytesRead = 0;
            ClientStatus status = DoAuthentification(clientStream);

            if (status.IsCorrect == true)
            {
                logger.info("Sensor module " + status.Id + " succesfully connected");
                children.Add(tcpClientStatusProvider);
                while (_isOn == true)
                {
                    bytesRead = 0;
                    try
                    {
                        //blocks until a client sends a message
                        bytesRead = clientStream.Read(message, 0, MAX_MESSAGE_SIZE);
                    }
                    catch (Exception e)
                    {
                        //a socket error has occured
                        logger.error("A socket error has occured while reading data from sensor module " + status.Id + "!", e);
                        break;
                    }
                    if (bytesRead == 0)
                    {
                        //the client has disconnected from the server
                        logger.info("Sensor module " + status.Id + " disconnected");
                        break;
                    }
                    //message has successfully been received
                    clientStream.Flush();
                    ASCIIEncoding encoder = new ASCIIEncoding();
                    string text = encoder.GetString(message, 0, bytesRead);
                    try
                    {
                        text = CipherUtility.Decrypt<AesManaged>(text,
                                                                 CipherUtility.DEFAULT_PASSWORD,
                                                                 CipherUtility.DEFAULT_SALT);
                        OnMsgReceived(new MsgReceivedEventArgs(status.Id, text));
                    }
                    catch (Exception e)
                    {
                        logger.warning("An exception occured while processing message from sensor module " + status.Id + "!", e);
                    }
                }
                children.Remove(tcpClientStatusProvider);
            }
            else
                logger.warning("Sensor module " + status.Id + " identification failed!");
            
            tcpClient.Close();
        }

        private ClientStatus DoAuthentification(NetworkStream clientStream)
        {
            /*This is a stub. Implement method with database autentification*/
            int bytesRead = 0;
            byte[] message = new byte[MAX_MESSAGE_SIZE];
            try
            {
                //blocks until a client sends a message
                bytesRead = clientStream.Read(message, 0, MAX_MESSAGE_SIZE);
                clientStream.Flush();
                //Message should contain the module ID
                ASCIIEncoding encoder = new ASCIIEncoding();
                string sMessage = encoder.GetString(message, 0, bytesRead);
                sMessage = CipherUtility.Decrypt<AesManaged>(sMessage,
                                                             CipherUtility.DEFAULT_PASSWORD,
                                                             CipherUtility.DEFAULT_SALT);

                Guid pacientId = new Guid(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(sMessage)));
                Pacient pacient = entities.Pacients.SingleOrDefault(p => p.idPacient.Equals(pacientId));
                if (pacient == null)
                    return new ClientStatus(sMessage, false);
                return new ClientStatus(sMessage, true);
            }
            catch
            {
                return new ClientStatus("0", false);
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Starts the severs
        /// </summary>
        public void Start()
        {
            logger.info("Starting TCP listener for sensor processing endpoint...");
            _isOn = true;
            _listenerThread.Start();
            startTime = DateTime.Now;
            lastAccessTime = startTime;
            logger.info("Sensor processing endpoint listener succesfully started");
        }

        public DateTime getStartTime()
        {
            return startTime;
        }

        public DateTime getLastAccessTime()
        {
            return lastAccessTime;
        }

        public ICollection<StatusProvider> getChildren()
        {
            return children;
        }
        /// <summary>
        /// Stops the server
        /// </summary>
        public void Stop()
        {
            _isOn = false;
            _listener.Stop();
        }
        #endregion
    }
}
